package 框架.security登录JWT加密_初级版;

import cn.tedu.csmall.commons.ex.ServiceException;
import cn.tedu.csmall.commons.pojo.po.AdminLoginInfoPO;
import cn.tedu.csmall.commons.pojo.vo.PageData;
import cn.tedu.csmall.commons.web.ServiceCode;
import cn.tedu.csmall.passport.dao.cache.repository.IAdminJwtRepository;
import cn.tedu.csmall.passport.dao.persist.repository.IAdminRepository;
import cn.tedu.csmall.passport.dao.persist.repository.IAdminRoleRepository;
import cn.tedu.csmall.passport.pojo.dto.AdminLoginInfoDTO;
import cn.tedu.csmall.passport.pojo.entity.Admin;
import cn.tedu.csmall.passport.pojo.entity.AdminRole;
import cn.tedu.csmall.passport.pojo.param.AdminAddNewParam;
import cn.tedu.csmall.passport.pojo.param.AdminLoginInfoParam;
import cn.tedu.csmall.passport.pojo.vo.AdminListItemVO;
import cn.tedu.csmall.passport.pojo.vo.AdminStandardVO;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;

/**
 * 处理管理员数据的业务实现类
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Service
public class AdminServiceImpl2 implements IAdminService {

    @Value("${csmall.crud.default-query-page-size}")
    private Integer defaultQueryPageSize;
    @Value("${csmall.jwt.secret-key}")
    private String secretKey;
    @Value("${csmall.jwt.duration-in-minute}")
    private Long durationInMinute;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private IAdminRepository adminRepository;
    @Autowired
    private IAdminRoleRepository adminRoleRepository;
    @Autowired
    private IAdminJwtRepository adminCacheRepository;
    @Autowired
    private AuthenticationManager authenticationManager;

    public AdminServiceImpl2() {
        log.debug("创建业务类对象：AdminServiceImpl");
    }

    @Override
    public AdminLoginInfoDTO login(AdminLoginInfoParam adminLoginInfoParam, String remoteAddr, String userAgent) {
        log.debug("开始处理【管理员登录】的业务，参数：{}", adminLoginInfoParam);
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                adminLoginInfoParam.getUsername(), adminLoginInfoParam.getPassword());
        Authentication authenticateResult
                = authenticationManager.authenticate(authentication);
        log.debug("认证通过！（如果未通过，过程中将抛出异常，你不会看到此条日志！）");
        log.debug("认证结果：{}", authenticateResult);
        log.debug("认证结果中的当事人：{}", authenticateResult.getPrincipal());
        AdminDetails adminDetails = (AdminDetails) authenticateResult.getPrincipal();

        // 使用JWT机制时，登录成功后不再需要将认证信息存入到SecurityContext
        // SecurityContext securityContext = SecurityContextHolder.getContext();
        // securityContext.setAuthentication(authenticateResult);

        // 需要存入到JWT中的数据
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", adminDetails.getId());
        claims.put("username", adminDetails.getUsername());
        String authorityListJsonString = JSON.toJSONString(adminDetails.getAuthorities());
        // claims.put("authoritiesJsonString", authorityListJsonString);

        // 生成JWT，以下代码是相对固定的
        long tokenDuration = durationInMinute * 60 * 1000;
        Date tokenExpiration = new Date(System.currentTimeMillis() + tokenDuration);
        String jwt = Jwts.builder()
                // Header：声明算法与Token类型
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                // Payload：数据，具体表现为Claims
                .setClaims(claims)
                .setExpiration(tokenExpiration)
                // Verify Signature：验证签名
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
        log.debug("生成了JWT数据：{}", jwt);

        // 需要缓存到Redis中的数据
        AdminLoginInfoPO adminLoginInfoPO = new AdminLoginInfoPO();
        adminLoginInfoPO.setId(adminDetails.getId());
        adminLoginInfoPO.setEnable(adminDetails.isEnabled() ? 1 : 0);
        adminLoginInfoPO.setRemoteAddr(remoteAddr);
        adminLoginInfoPO.setUserAgent(userAgent);
        adminLoginInfoPO.setAuthorityListJsonString(authorityListJsonString);
        adminCacheRepository.saveLoginInfo(jwt, adminLoginInfoPO, tokenDuration);
        log.debug("向Redis中写入此管理员的登录信息：{}", adminLoginInfoPO);

        // 准备返回
        AdminLoginInfoDTO adminLoginInfoDTO = new AdminLoginInfoDTO()
                .setId(adminDetails.getId())
                .setUsername(adminDetails.getUsername())
                .setAvatar(adminDetails.getAvatar())
                .setToken(jwt)
                .setTokenExpiration(tokenExpiration);
        return adminLoginInfoDTO;
    }

    @Override
    public void logout(String jwt) {
        log.debug("开始处理【管理员退出登录】的请求，参数：{}", jwt);
       adminCacheRepository.delete(jwt);
    }

    @Override
    public void addNew(AdminAddNewParam adminAddNewParam) {
        log.debug("开始处理【添加管理员】的业务，参数：{}", adminAddNewParam);

        // 检查用户名是否被占用
        {
            // 从参数对象中获取管理员的用户名
            String username = adminAddNewParam.getUsername();
            // 调用Mapper对象的countByUsername()执行统计
            int count = adminRepository.countByUsername(username);
            // 判断统计结果是否大于0
            if (count > 0) {
                // 是：抛出异常
                String message = "添加管理员失败，用户名已经被占用！";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERROR_CONFLICT, message);
            }
        }

        // 检查手机号码是否被占用
        {
            String phone = adminAddNewParam.getPhone();
            int count = adminRepository.countByPhone(phone);
            if (count > 0) {
                String message = "添加管理员失败，手机号码已经被占用！";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERROR_CONFLICT, message);
            }
        }

        // 检查电子邮箱是否被占用
        {
            String email = adminAddNewParam.getEmail();
            int count = adminRepository.countByEmail(email);
            if (count > 0) {
                String message = "添加管理员失败，电子邮箱已经被占用！";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERROR_CONFLICT, message);
            }
        }

        // 创建Admin对象
        Admin admin = new Admin();
        // 复制属性
        BeanUtils.copyProperties(adminAddNewParam, admin);
        // 补全Admin对象的属性值：lastLoginIP(null) / loginCount(0) / gmtLastLogin(null) / gmtCreate / gmtModified
        LocalDateTime now = LocalDateTime.now();
        admin.setGmtCreate(now);
        admin.setGmtModified(now);
        admin.setLoginCount(0);
        // 从Admin对象中取出原密码，执行加密，再将密文存入到Admin对象中
        String rawPassword = admin.getPassword();
        String encodedPassword = passwordEncoder.encode(rawPassword);
        admin.setPassword(encodedPassword);
        // 调用Mapper对象的insert()执行插入
        int rows = adminRepository.insert(admin);
        if (rows != 1) {
            String message = "添加管理员失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_INSERT, message);
        }

        // 为新添加的管理员分配角色
        // 基于角色ID的数组来创建List<AdminRole>
        Long[] roleIds = adminAddNewParam.getRoleIds();
        List<AdminRole> adminRoleList = new ArrayList<>();
        for (Long roleId : roleIds) {
            AdminRole adminRole = new AdminRole();
            adminRole.setAdminId(admin.getId());
            adminRole.setRoleId(roleId);
            adminRoleList.add(adminRole);
        }
        // 调用AdminRoleMapper对象的insertBatch()执行插入
        rows = adminRoleRepository.insertBatch(adminRoleList);
        if (rows < 1) {
            String message = "添加管理员失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_INSERT, message);
        }

    }

    @Override
    public void delete(Long id) {
        log.debug("开始处理【根据id删除管理员】的业务，参数：{}", id);
        // 检查尝试删除的数据是否存在
        Object queryResult = adminRepository.getStandardById(id);
        if (queryResult == null) {
            String message = "删除管理员失败，尝试访问的数据不存在！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_NOT_FOUND, message);
        }

        // 执行删除--管理员表
        log.debug("即将执行删除数据，参数：{}", id);
        int rows = adminRepository.deleteById(id);
        if (rows != 1) {
            String message = "删除管理员失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_DELETE, message);
        }

        // 执行删除--管理员与角色的关联表
        log.debug("即将执行删除关联数据，参数：{}", id);
        rows = adminRoleRepository.deleteByAdminId(id);
        if (rows < 1) {
            String message = "删除管理员失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_DELETE, message);
        }
    }

    @Override
    public void setEnable(Long id) {
        updateEnableById(id, 1);
    }

    @Override
    public void setDisable(Long id) {
        updateEnableById(id, 0);
    }

    @Override
    public PageData<AdminListItemVO> list(Integer pageNum) {
        log.debug("开始处理【查询管理员列表】的业务，页码：{}", pageNum);
        PageData<AdminListItemVO> pageData = adminRepository.list(pageNum, defaultQueryPageSize);
        return pageData;
    }

    @Override
    public PageData<AdminListItemVO> list(Integer pageNum, Integer pageSize) {
        log.debug("开始处理【查询管理员列表】的业务，页码：{}，每页记录数：{}", pageNum, pageSize);
        PageData<AdminListItemVO> pageData = adminRepository.list(pageNum, pageSize);
        return pageData;
    }

    private void updateEnableById(Long id, Integer enable) {
        log.debug("开始处理【{}管理员】的业务，ID：{}，目标状态：{}", ENABLE_TEXT[enable], id, enable);
        // 检查数据是否存在
        AdminStandardVO queryResult = adminRepository.getStandardById(id);
        if (queryResult == null) {
            String message = ENABLE_TEXT[enable] + "管理员失败，尝试访问的数据不存在！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_NOT_FOUND, message);
        }

        // 检查当前状态是否与参数表示的状态相同
        if (queryResult.getEnable().equals(enable)) {
            String message = ENABLE_TEXT[enable] + "管理员失败，当前管理员已经处于"
                    + ENABLE_TEXT[enable] + "状态！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_CONFLICT, message);
        }

        // 准备执行更新
        Admin admin = new Admin();
        admin.setId(id);
        admin.setEnable(enable);
        log.debug("即将修改数据，参数：{}", admin);
        int rows = adminRepository.updateById(admin);
        if (rows != 1) {
            String message = ENABLE_TEXT[enable] + "管理员失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_UPDATE, message);
        }
    }

}
